import { readFileSync, existsSync } from 'fs';
import { load } from 'js-yaml';
import { z } from 'zod';
import { RabbitMQConfig, ConnectionOptions } from '@/types';

// Zod schema for configuration validation
const RabbitMQConfigSchema = z.object({
  host: z.string().default('localhost'),
  port: z.number().int().min(1).max(65535).default(5672),
  username: z.string().default('guest'),
  password: z.string().default('guest'),
  virtualHost: z.string().default('/'),
  heartbeat: z.number().int().min(0).default(600),
  connectionTimeout: z.number().int().min(0).default(30000),
  sslEnabled: z.boolean().default(false),
  sslOptions: z.record(z.unknown()).default({}),
  maxRetries: z.number().int().min(0).default(3),
  retryDelay: z.number().int().min(0).default(1000),
});

export class ConfigManager {
  private config: RabbitMQConfig;
  private readonly envPrefix: string;

  constructor(configFile?: string, envPrefix = 'RABBITMQ_') {
    this.envPrefix = envPrefix;
    this.config = this.loadConfig(configFile);
  }

  private loadConfig(configFile?: string): RabbitMQConfig {
    let config: Partial<RabbitMQConfig> = {};

    // Load from file if provided
    if (configFile && existsSync(configFile)) {
      try {
        const fileContent = readFileSync(configFile, 'utf8');
        const fileConfig = configFile.endsWith('.json')
          ? JSON.parse(fileContent)
          : load(fileContent);
        config = { ...config, ...fileConfig };
      } catch (error) {
        throw new Error(`Failed to load config file ${configFile}: ${error}`);
      }
    }

    // Override with environment variables
    const envConfig = this.loadFromEnvironment();
    config = { ...config, ...envConfig };

    // Validate and return
    try {
      return RabbitMQConfigSchema.parse(config);
    } catch (error) {
      if (error instanceof z.ZodError) {
        const issues = error.issues.map(issue => `${issue.path.join('.')}: ${issue.message}`);
        throw new Error(`Configuration validation failed:\n${issues.join('\n')}`);
      }
      throw error;
    }
  }

  private loadFromEnvironment(): Partial<RabbitMQConfig> {
    const envConfig: Partial<RabbitMQConfig> = {};

    // Map environment variables to config properties
    const envMappings: Record<string, keyof RabbitMQConfig> = {
      HOST: 'host',
      PORT: 'port',
      USERNAME: 'username',
      PASSWORD: 'password',
      VIRTUAL_HOST: 'virtualHost',
      HEARTBEAT: 'heartbeat',
      CONNECTION_TIMEOUT: 'connectionTimeout',
      SSL_ENABLED: 'sslEnabled',
      MAX_RETRIES: 'maxRetries',
      RETRY_DELAY: 'retryDelay',
    };

    for (const [envKey, configKey] of Object.entries(envMappings)) {
      const envValue = process.env[`${this.envPrefix}${envKey}`];
      if (envValue !== undefined) {
        // Type conversion based on config key
        switch (configKey) {
          case 'port':
          case 'heartbeat':
          case 'connectionTimeout':
          case 'maxRetries':
          case 'retryDelay':
            envConfig[configKey] = parseInt(envValue, 10);
            break;
          case 'sslEnabled':
            envConfig[configKey] = envValue.toLowerCase() === 'true';
            break;
          default:
            (envConfig as Record<string, unknown>)[configKey] = envValue;
        }
      }
    }

    // Handle SSL options separately
    const sslOptionsEnv = process.env[`${this.envPrefix}SSL_OPTIONS`];
    if (sslOptionsEnv) {
      try {
        envConfig.sslOptions = JSON.parse(sslOptionsEnv);
      } catch {
        // Ignore invalid JSON
      }
    }

    return envConfig;
  }

  public getRabbitMQConfig(): RabbitMQConfig {
    return { ...this.config };
  }

  public updateConfig(updates: Partial<RabbitMQConfig>): void {
    const newConfig = { ...this.config, ...updates };
    try {
      this.config = RabbitMQConfigSchema.parse(newConfig);
    } catch (error) {
      if (error instanceof z.ZodError) {
        const issues = error.issues.map(issue => `${issue.path.join('.')}: ${issue.message}`);
        throw new Error(`Configuration update validation failed:\n${issues.join('\n')}`);
      }
      throw error;
    }
  }

  public getConnectionUrl(): string {
    const { host, port, username, password, virtualHost } = this.config;
    // For virtual host, we need to handle the leading slash properly
    // If virtualHost starts with '/', we keep it and encode the rest
    // Otherwise, we add '/' and encode the whole thing
    let encodedVirtualHost: string;
    if (virtualHost.startsWith('/')) {
      const pathPart = virtualHost.slice(1); // Remove leading slash
      encodedVirtualHost = '/' + encodeURIComponent(pathPart);
    } else {
      encodedVirtualHost = '/' + encodeURIComponent(virtualHost);
    }
    return `amqp://${username}:${password}@${host}:${port}${encodedVirtualHost}`;
  }

  public getConnectionOptions(): ConnectionOptions {
    const { heartbeat, connectionTimeout, sslEnabled, sslOptions } = this.config;

    const options: ConnectionOptions = {
      heartbeat,
      timeout: connectionTimeout,
    };

    if (sslEnabled) {
      options.protocol = 'amqps';
      options.ssl = sslOptions;
    }

    return options;
  }
}

// Default configuration instance
export const defaultConfig = new ConfigManager();

// Configuration validation utilities
export const validateConfig = (config: unknown): RabbitMQConfig => {
  return RabbitMQConfigSchema.parse(config);
};

export const isValidConfig = (config: unknown): config is RabbitMQConfig => {
  try {
    RabbitMQConfigSchema.parse(config);
    return true;
  } catch {
    return false;
  }
};
